//////////////////////////////////////////////////////////////////////////
#include <FileSystem/FileSystem.hpp>
#include <physfs.h>

#ifdef OS_WINDOWS
#	include <direct.h>
#else
#	include <unistd.h>
#endif


using namespace FileSystem;


// Obviously not multithread safe etc...
static bool	sInitialized	=	false;


Initializer::Initializer( const char* arg0, const std::string& company, const std::string& game, const std::string& arcExt )
{
	assert( sInitialized==false );

	if( PHYSFS_init( arg0 ) )
	{
		sInitialized	=	true;

		// Moving the initialization of directory structure here.
		// TODO: Eventually setup for customization.
		if( FileSystem::CreateAndActivate( company, game ) )
		{
			// Make the games writable directory part of the search path.
			FileSystem::Mount( FileSystem::GameLocalStorage( company, game ) );

#ifdef ENABLE_DEVBUILD
			// In devbuilds we use the current working directory as the source of
			// pre-built assets.  The expected file is a zip named the same as the
			// game.  If this fails, no worries, guess nothing is compiled yet.
			FileSystem::Mount( FileSystem::CurrentDirectory() + "/" + game + "." + arcExt );

			// We also use a relative 'Source' directory for any content to override
			// the files in the asset file.
			FileSystem::Mount( FileSystem::CurrentDirectory() + "/../Source/" + game );

			// Devbuilds dump out the mounted paths.
			FileSystem::SearchPaths(
				[]( const std::string& sp )
				{ LOG( INFO ) << "Mount point: " << sp; }
			);

#else
			// In non-dev builds we use the location of the executable to find the
			// asset file.
			FileSystem::Mount( FileSystem::BaseDirectory() + game + "." + arcExt );
#endif
		}
		else
		{
			sInitialized	=	false;
			PHYSFS_deinit();
			assert( false );
			return;
		}

		return;
	}

	// Throw an exception or something eventually.
	assert( false );
}


Initializer::~Initializer()
{
	assert( sInitialized==true );

	PHYSFS_deinit();
	sInitialized	=	false;
}


//////////////////////////////////////////////////////////////////////////
const char FileSystem::DirSeparator()
{
	return *PHYSFS_getDirSeparator();
}


std::string FileSystem::CurrentDirectory()
{
	char	wd[ 1024 ];
#	ifdef OS_WINDOWS
	_getcwd( wd, 1024 );
#	else
	getcwd( wd, 1024 );
#	endif

	return std::string( wd );
}


std::string FileSystem::BaseDirectory()
{
	std::string	result( PHYSFS_getBaseDir() );
	return result;
}


std::string FileSystem::UserDirectory()
{
	std::string	result( PHYSFS_getUserDir() );
	return result;
}


std::string FileSystem::GameLocalStorage( const std::string& comp, const std::string& game )
{
	const std::string	period( "." );
	return FileSystem::UserDirectory() + period + comp + "/" + game;
}


bool FileSystem::CreateAndActivate( const std::string& comp, const std::string& game )
{
	const std::string	period( "." );
	if( !FileSystem::WriteDirectory( FileSystem::UserDirectory() ) )
		return false;

	// Create the .comp/game directory if needed.
	if( !FileSystem::MakeDirectory( period + comp + "/" + game ) )
		return false;

	// Descend into the game specific folder.
	if( !FileSystem::WriteDirectory( FileSystem::UserDirectory() + period + comp + "/" + game ) )
		return false;

	return true;
}


std::string FileSystem::WriteDirectory()
{
	std::string	result( PHYSFS_getWriteDir() );
	return result;
}


bool FileSystem::WriteDirectory( const std::string& wd )
{
	return PHYSFS_setWriteDir( wd.c_str() )!=0;
}


bool FileSystem::Mount( const std::string& item, const std::string& loc, bool prepend )
{
	return PHYSFS_mount( item.c_str(), loc.empty() ? nullptr : loc.c_str(), prepend ? 0 : 1 )!=0;
}


bool FileSystem::RemoveSearchPath( const std::string& sp )
{
	return PHYSFS_removeFromSearchPath( sp.c_str() )!=0;
}


bool FileSystem::Exists( const std::string& f )
{
	return PHYSFS_exists( f.c_str() )!=0;
}


bool FileSystem::IsDirectory( const std::string& f )
{
	return PHYSFS_isDirectory( f.c_str() )!=0;
}


bool FileSystem::MakeDirectory( const std::string& d )
{
	return PHYSFS_mkdir( d.c_str() )!=0;
}


bool FileSystem::Delete( const std::string& f )
{
	return PHYSFS_delete( f.c_str() )!=0;
}


int64_t FileSystem::LastModTime( const std::string& f )
{
	return PHYSFS_getLastModTime( f.c_str() );
}


std::string FileSystem::RealDirectory( const std::string& d )
{
	std::string	result( PHYSFS_getRealDir( d.c_str() ) );
	return result;
}


File_t FileSystem::Open( const std::string& f, OpenMode m )
{
	switch( m )
	{
	case OpenMode::kRead:
		return File_t( PHYSFS_openRead( f.c_str() ) );
	case OpenMode::kAppend:
		return File_t( PHYSFS_openAppend( f.c_str() ) );
	case OpenMode::kWrite:
		return File_t( PHYSFS_openWrite( f.c_str() ) );
	}

	return 0;
}


bool FileSystem::Close( File_t f )
{
	return PHYSFS_close( (PHYSFS_File*)f )!=0;
}


int64_t FileSystem::Read( File_t f, void* buffer, size_t size )
{
	return PHYSFS_read( (PHYSFS_File*)f, buffer, 1, size );
}


int64_t FileSystem::Write( File_t f, const void* buffer, size_t size )
{
	return PHYSFS_write( (PHYSFS_File*)f, buffer, 1, size );
}


bool FileSystem::Eof( File_t f )
{
	return PHYSFS_eof( (PHYSFS_File*)f )!=0;
}


bool FileSystem::Seek( File_t f, uint64_t p )
{
	return PHYSFS_seek( (PHYSFS_File*)f, p )!=0;
}


int64_t FileSystem::Length( File_t f )
{
	return PHYSFS_fileLength( (PHYSFS_File*)f );
}


bool FileSystem::Flush( File_t f )
{
	return PHYSFS_flush( (PHYSFS_File*)f )!=0;
}


//////////////////////////////////////////////////////////////////////////
static void FileCallback( void* fi, const char* orig, const char* fname )
{
	NameCallback_t*	pfi	=	(NameCallback_t*)fi;
	if( !PHYSFS_isDirectory( fname ) )
		(*pfi)( fname );
}


void FileSystem::FileList( const std::string& dir, NameCallback_t fi )
{
	if( fi )
		PHYSFS_enumerateFilesCallback( dir.c_str(), FileCallback, &fi );
}


//////////////////////////////////////////////////////////////////////////
static void DirCallback( void* fi, const char* orig, const char* fname )
{
	NameCallback_t*	pfi	=	(NameCallback_t*)fi;
	if( PHYSFS_isDirectory( fname ) )
		(*pfi)( fname );
}


void FileSystem::DirList( const std::string& dir, NameCallback_t di )
{
	if( di )
		PHYSFS_enumerateFilesCallback( dir.c_str(), DirCallback, &di );
}


//////////////////////////////////////////////////////////////////////////
static void PathCallback( void* data, const char* str )
{
	NameCallback_t*	pfi	=	(NameCallback_t*)data;
	(*pfi)( str );
}

void FileSystem::SearchPaths( NameCallback_t spi )
{
	if( spi )
		PHYSFS_getSearchPathCallback( PathCallback, &spi );
}


//////////////////////////////////////////////////////////////////////////
// Preliminary lua file stuff.
std::string FileSystem::ReadFileAsString( const std::string& fn )
{
	std::string	result;
	FileSystem::File_t	confFile	=	FileSystem::Open( fn, OpenMode::kRead );

	if( confFile )
	{
		int64_t	length	=	FileSystem::Length( confFile );
		char*	buffer	=	new char[ size_t( length+1 ) ];

		if( buffer )
		{
			buffer[ length ]	=	0;

			FileSystem::Read( confFile, buffer, size_t( length ) );
			result	=	buffer;
			FileSystem::Close( confFile );
		}

		delete[] buffer;
	}

	return result;
}
